﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using iNet.IO;
using iNet.Tasks;

namespace iNet.Net
{
	/// <summary>
	/// 檔案上傳工作。
	/// </summary>
	public class FileUploadTask : Task
	{
		#region 欄位
		string _Method;
		Stream _ResponseStream;
		string _SourceFilePath;
		Stream _SourceStream;
		Uri _UploadUri;
		WebClient _WebClient = new WebClient();
		#endregion
		#region 建構子
		/// <summary>
		/// 初始化新的 FileUploadTask 執行個體。
		/// </summary>
		public FileUploadTask()
		{
			this.InitialProgress = 0;
		}
		/// <summary>
		/// 初始化新的 FileUploadTask 執行個體。
		/// </summary>
		/// <param name="uploadUri">上傳的目標網路位址。</param>
		/// <param name="sourceFilePath">欲上傳的來源檔案路徑。</param>
		public FileUploadTask(Uri uploadUri, string sourceFilePath)
			: this()
		{
			_UploadUri = uploadUri;
			_SourceFilePath = sourceFilePath;
		}
		/// <summary>
		/// 初始化新的 FileUploadTask 執行個體。
		/// </summary>
		/// <param name="uploadUri">上傳的目標網路位址。</param>
		/// <param name="sourceStream">欲上傳的來源資料流。</param>
		public FileUploadTask(Uri uploadUri, Stream sourceStream)
			: this()
		{
			_UploadUri = uploadUri;
			_SourceStream = sourceStream;
		}
		#endregion
		#region Credentials
		/// <summary>
		/// 取得或設定傳送至伺服器的網路認證。
		/// </summary>
		public ICredentials Credentials
		{
			get
			{
				return _WebClient.Credentials;
			}
			set
			{
				lock (this.SyncRoot)
				{
					if (this.State != TaskState.Initializing)
						throw new InvalidOperationException();
					_WebClient.Credentials = value;
				}
			}
		}
		#endregion
		#region Execute
		/// <summary>
		/// 執行工作。
		/// </summary>
		/// <param name="version">工作版本。</param>
		/// <returns>True 表示工作執行成功，False 表示失敗。</returns>
		protected override bool Execute(TaskVersion version)
		{
			//取得參數
			Stream sourceStream;
			string sourceFilePath;
			if (!this.ReadValue<string>(version, ref _SourceFilePath, out sourceFilePath)
				|| !this.ReadValue<Stream>(version, ref _SourceStream, out sourceStream))
			{
				return true;
			}

			//上傳檔案
			bool isTempFile = (sourceStream != null);
			try
			{
				//建立暫存檔
				if (isTempFile)
				{
					sourceFilePath = Path.GetTempFileName();
					using (Stream stream = new FileStream(sourceFilePath, FileMode.OpenOrCreate, FileAccess.Write))
					{
						StreamCopyTask copyTask = new StreamCopyTask(sourceStream, stream);
						copyTask.ProgressChanged += (sender, e) =>
						{
							this.ReportProgress(version, (e.Progress * 0.5));
						};
						switch (this.ExecuteChildTask(version, copyTask))
						{
							case TaskState.Executed:
								this.ReportProgress(version, 50);
								break;
							case TaskState.Canceled:
								return true;
							default:
								return false;
						}
					}
				}

				//準備上傳
				MemoryStream responseStream = null;
				Exception exception = null;
				ManualResetEventSlim waitHandle = new ManualResetEventSlim();
				UploadFileCompletedEventHandler completedHandler = (sender, e) =>
				{
					try
					{
						responseStream = new MemoryStream(e.Result);
					}
					catch
					{ }
					finally
					{
						exception = e.Error;
						try
						{
							waitHandle.Set();
						}
						catch
						{ }
					}
				};
				UploadProgressChangedEventHandler progressChangedHandler = (sender, e) =>
				{
					double ratio = ((double)e.BytesSent / e.TotalBytesToSend);
					if (isTempFile)
						this.ReportProgress(version, 50 + 50 * ratio);
					else
						this.ReportProgress(version, 100 * ratio);
				};
				_WebClient.UploadFileCompleted += completedHandler;
				_WebClient.UploadProgressChanged += progressChangedHandler;

				//上傳檔案
				try
				{
					//上傳檔案
					_WebClient.UploadFileAsync(_UploadUri, _Method, sourceFilePath);
					waitHandle.Wait();

					//中斷確認
					if (this.IsCanceling)
						return true;

					//儲存結果
					if (exception != null)
						throw exception;
					this.WriteValue<Stream>(version, ref _ResponseStream, responseStream);
				}
				finally
				{
					waitHandle.Dispose();
					_WebClient.UploadFileCompleted -= completedHandler;
					_WebClient.UploadProgressChanged -= progressChangedHandler;
				}
			}
			finally
			{
				//刪除暫存檔
				if (isTempFile && sourceFilePath != null)
				{
					try
					{
						File.Delete(sourceFilePath);
					}
					catch
					{ }
				}
			}

			//完成
			return true;
		}
		#endregion
		#region Headers
		/// <summary>
		/// 取得或設定與網路請求相關的標頭名稱/值組集合。
		/// </summary>
		public WebHeaderCollection Headers
		{
			get
			{
				return _WebClient.Headers;
			}
			set
			{
				lock (this.SyncRoot)
				{
					if (this.State != TaskState.Initializing)
						throw new InvalidOperationException();
					_WebClient.Headers = value;
				}
			}
		}
		#endregion
		#region Method
		/// <summary>
		/// 取得或設定上傳時所使用的方法，Null (Visual Basic 為 Nothing) 表示使用預設值，HTTP 為 POST；FTP 為 STOR。
		/// </summary>
		public string Method
		{
			get
			{
				return _Method;
			}
			set
			{
				this.SetInitializingParameter<string>(ref _Method, value);
			}
		}
		#endregion
		#region OnCanceling
		/// <summary>
		/// 引發 Canceling 事件。
		/// </summary>
		/// <param name="e">事件資料。</param>
		protected override void OnCanceling(TaskEventArgs e)
		{
			//取消上傳
			_WebClient.CancelAsync();

			//呼叫基底
			base.OnCanceling(e);
		}
		#endregion
		#region Proxy
		/// <summary>
		/// 取得或設定連線所使用的 Proxy。
		/// </summary>
		public IWebProxy Proxy
		{
			get
			{
				return _WebClient.Proxy;
			}
			set
			{
				lock (this.SyncRoot)
				{
					if (this.State != TaskState.Initializing)
						throw new InvalidOperationException();
					_WebClient.Proxy = value;
				}
			}
		}
		#endregion
		#region ResponseHeaders
		/// <summary>
		/// 取得來自伺服器回應的標頭名稱/值組集合。
		/// </summary>
		public WebHeaderCollection ResponseHeaders
		{
			get
			{
				return _WebClient.ResponseHeaders;
			}
		}
		#endregion
		#region ResponseStream
		/// <summary>
		/// 取得來自伺服器的回應資料流。
		/// </summary>
		public Stream ResponseStream
		{
			get
			{
				return _ResponseStream;
			}
		}
		#endregion
		#region SourceFilePath
		/// <summary>
		/// 取得或設定欲上傳的來源檔案路徑。
		/// </summary>
		public string SourceFilePath
		{
			get
			{
				return _SourceFilePath;
			}
			set
			{
				this.SetInitializingParameter<string>(ref _SourceFilePath, value);
			}
		}
		#endregion
		#region SourceStream
		/// <summary>
		/// 取得或設定欲上傳的來源資料流。
		/// </summary>
		public Stream SourceStream
		{
			get
			{
				return _SourceStream;
			}
			set
			{
				this.SetInitializingParameter<Stream>(ref _SourceStream, value);
			}
		}
		#endregion
		#region UploadUri
		/// <summary>
		/// 取得或設定上傳的目標網路位址。
		/// </summary>
		public Uri UploadUri
		{
			get
			{
				return _UploadUri;
			}
			set
			{
				this.SetInitializingParameter<Uri>(ref _UploadUri, value);
			}
		}
		#endregion
	}
}
